Completed
Pull Request — develop (#75)
by
unknown
01:02
created

forcegraph.js ➔ ... ➔ d3Drag.drag.end   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
nc 2
dl 0
loc 5
rs 9.4285
nop 0
1
define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'forcegraph/math', 'forcegraph/draw'], function (d3Selection, d3Force, d3Zoom, d3Drag, math, draw) {
2
  'use strict';
3
4
  return function (config, linkScale, sidebar, router) {
5
    var self = this;
6
    var el;
7
    var canvas;
8
    var ctx;
9
    var force;
10
    var forceLink;
11
12
    var transform = d3Zoom.zoomIdentity;
13
    var intNodes = [];
14
    var dictNodes = {};
15
    var intLinks = [];
16
17
    const NODE_RADIUS_DRAG = 10;
18
    const NODE_RADIUS_SELECT = 15;
19
    const LINK_RADIUS_SELECT = 12;
20
21
    const ZOOM_MIN = 1 / 4;
22
    const ZOOM_MAX = 3;
23
24
    draw.setTransform(transform);
25
26
    function resizeCanvas() {
27
      canvas.width = el.offsetWidth;
28
      canvas.height = el.offsetHeight;
29
      canvas.style.width = el.offsetWidth + 'px';
30
      canvas.style.height = el.offsetHeight + 'px';
31
    }
32
33
    function moveTo(x, y) {
34
      transform.x = (canvas.width + sidebar()) / 2  - x * transform.k;
35
      transform.y =  canvas.height / 2              - y * transform.k;
36
    }
37
38
    function onClick() {
39
      if (d3Selection.event.defaultPrevented) {
40
        return;
41
      }
42
43
      var e = transform.invert([d3Selection.event.clientX, d3Selection.event.clientY]);
44
      var n = force.find(e[0], e[1], NODE_RADIUS_SELECT);
45
46
      if (n !== undefined) {
47
        router.node(n.o.node)();
48
        return;
49
      }
50
51
      e = {x: e[0], y: e[1]};
52
53
54
      var closedLink;
55
      var radius = LINK_RADIUS_SELECT;
56
      intLinks
57
        /* Disable Clickable VPN
58
        .filter(function (d) {
59
          return d.o.type !== 'fastd' && d.o.type !== 'L2TP';
60
        })
61
        */
62
        .forEach(function (d) {
63
          var distance = math.distanceLink(e, d.source, d.target);
64
          if (distance < radius) {
65
            closedLink = d;
66
            radius = distance;
67
          }
68
        });
69
70
      if (closedLink !== undefined) {
71
        router.link(closedLink.o)();
72
      }
73
    }
74
75
    function redraw() {
76
      ctx.save();
77
      ctx.clearRect(0, 0, canvas.width, canvas.height);
78
      ctx.translate(transform.x, transform.y);
79
      ctx.scale(transform.k, transform.k);
80
81
      intLinks.forEach(draw.drawLink);
82
      intNodes.forEach(draw.drawNode);
83
84
      ctx.restore();
85
    }
86
87
    el = document.createElement('div');
88
    el.classList.add('graph');
89
90
    forceLink = d3Force.forceLink()
91
     .distance(function (d) {
92
       if (d.o.type === 'fastd' || d.o.type === 'L2TP') {
93
         return 0;
94
       }
95
       return 75;
96
     })
97
     .strength(function (d) {
98
       if (d.o.type === 'fastd' || d.o.type === 'L2TP') {
99
         return 0.02;
100
       }
101
       return Math.max(0.5, 1 / d.o.tq);
102
     });
103
104
    var zoom = d3Zoom.zoom()
105
         .scaleExtent([ZOOM_MIN, ZOOM_MAX])
106
         .on('zoom', function () {
107
           transform = d3Selection.event.transform;
108
           draw.setTransform(transform);
109
           redraw();
110
         });
111
112
113
    force = d3Force.forceSimulation()
114
      .force('link', forceLink)
115
      .force('charge', d3Force.forceManyBody())
116
      .on('tick', redraw);
117
118
    var drag = d3Drag.drag()
119
      .subject(function () {
120
        var e = transform.invert([d3Selection.event.x, d3Selection.event.y]);
121
        var n = force.find(e[0], e[1], NODE_RADIUS_DRAG);
122
123
        if (n !== undefined) {
124
          if (!d3Selection.event.active) {
125
            force.alphaTarget(0.1).restart();
126
          }
127
          n.x = transform.applyX(n.x);
128
          n.y = transform.applyY(n.y);
129
          return n;
130
        }
131
        return undefined;
132
      })
133
      .on('drag', function () {
134
        d3Selection.event.subject.x = transform.invertX(d3Selection.event.x);
135
        d3Selection.event.subject.y = transform.invertY(d3Selection.event.y);
136
      })
137
      .on('end', function () {
138
        if (!d3Selection.event.active) {
139
          force.alphaTarget(0);
140
        }
141
      });
142
143
    canvas = d3Selection.select(el)
144
      .append('canvas')
145
      .on('click', onClick)
146
      .call(drag)
147
      .call(zoom)
148
      .node();
149
150
    ctx = canvas.getContext('2d');
151
    draw.setCTX(ctx);
152
153
    window.addEventListener('resize', function () {
154
      resizeCanvas();
155
      redraw();
156
    });
157
158
    self.setData = function setData(data) {
159
      intNodes = data.graph.nodes.map(function (d) {
160
        var e;
161
        if (d.id in dictNodes) {
162
          e = dictNodes[d.id];
163
        } else {
164
          e = {};
165
          dictNodes[d.id] = e;
166
        }
167
168
        e.o = d;
169
170
        return e;
171
      });
172
173
      intLinks = data.graph.links.map(function (d) {
174
        var e = {};
175
        e.o = d;
176
        e.source = dictNodes[d.source.id];
177
        e.target = dictNodes[d.target.id];
178
        e.color = linkScale(d.tq).hex();
179
180
        return e;
181
      });
182
183
      force.nodes(intNodes);
184
      forceLink.links(intLinks);
185
186
      force.restart();
187
      resizeCanvas();
188
    };
189
190
    self.resetView = function resetView() {
191
      draw.setHighlight(null);
192
      transform.k = (ZOOM_MIN + 1) / 2;
193
      moveTo( (canvas.width / 2) - sidebar(), canvas.height / 2);
194
      redraw();
195
    };
196
197
    self.gotoNode = function gotoNode(d) {
198
      draw.setHighlight({ type: 'node', o: d });
199
200
      for (var i = 0; i < intNodes.length; i++) {
201
        var n = intNodes[i];
202
        if ( n.o.node !== d) {
203
          continue;
204
        }
205
        transform.k = (ZOOM_MAX + 1) / 2;
206
        moveTo(n.x, n.y);
207
        break;
208
      }
209
      redraw();
210
    };
211
212
    self.gotoLink = function gotoLink(d) {
213
      draw.setHighlight({ type: 'link', o: d });
214
      for (var i = 0; i < intLinks.length; i++) {
215
        var l = intLinks[i];
216
        if ( l.o !== d) {
217
          continue;
218
        }
219
        moveTo( (l.source.x + l.target.x) / 2, (l.source.y + l.target.y) / 2);
220
        break;
221
      }
222
      redraw();
223
    };
224
225
    self.destroy = function destroy() {
226
      force.stop();
227
      canvas.remove();
228
      force = null;
229
230
      if (el.parentNode) {
231
        el.parentNode.removeChild(el);
232
      }
233
    };
234
235
    self.render = function render(d) {
236
      d.appendChild(el);
237
      resizeCanvas();
238
    };
239
240
    return self;
241
  };
242
});
243